﻿// File:    HttpClient.cs
// Author:  jenvin
// Created: 2011年12月1日
// Purpose: Definition of Class HttpClient
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Web;
using System.Threading;
using System.Security;
using System.Globalization;
using System.Collections.Specialized;


namespace Utils
{
    /// <summary>
    /// 
    /// </summary>
    public class HttpClient
    {
        public const int DEFAULT_COPY_BUFFER_LENGTH = 8192;

        public const int DEFAULT_DOWNLOAD_BUFFER_LENGTH = 65536;

        public const string DEFAULT_UPLOADFILE_CONTENTTYPE = "application/octet-stream";

        public const string UPLOAD_FILE_CONTENTTYPE = "multipart/form-data";
        /// <summary>
        /// 默认 contexttype
        /// </summary>
        public const string DEFAULT_CONTENTTYPE = "application/x-www-form-urlencoded";
        /// <summary>
        /// 默认超时间
        /// </summary>
        public const int DEFAULT_TIMEOUT = 30000;
        /// <summary>
        /// 默认Agent
        /// </summary>
        public const string DEFAULT_USERAGENT = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
        /// <summary>
        /// CharSet 
        /// </summary>
        protected static Regex m_regCharSet = new Regex("<meta([^<]*)charset=\"?([^<]*)\"", RegexOptions.IgnoreCase | RegexOptions.Multiline);

        private IPAddress m_localIp = IPAddress.Any;
        public IPAddress LocalIp
        {
            get
            {
                return m_localIp;
            }
            set
            {
                m_localIp = value;
            }
        }
        /// <summary>
        /// 票据
        /// </summary>
        public ICredentials Credentials
        {
            get;
            set;
        }
        /// <summary>
        /// Cookie 容器
        /// </summary>
        public CookieContainer CookieContainer
        {
            get;
            set;
        }
        /// <summary>
        /// 请求Http头
        /// </summary>
        public WebHeaderCollection RequestHeaders
        {
            get;
            set;
        }

        public bool AutoRedirect
        {
            get;
            set;
        }
        /// <summary>
        /// 
        /// </summary>
        public int TimeOut
        {
            get;
            set;
        }

        public string ContentType
        {
            get;
            set;
        }

        public Encoding Encode
        {
            get;
            set;
        }

        public string Referer
        {
            get;
            set;
        }

        public IWebProxy Proxy
        {
            get;
            set;
        }

        #region Response 响应信息
        /// <summary>
        /// 响应Http头
        /// </summary>
        public WebHeaderCollection ResponseHeaders
        {
            get;
            set;
        }
        private HttpStatusCode m_ResponseStatusCode = HttpStatusCode.OK;
        public HttpStatusCode ResponseStatusCode
        {
            get { return m_ResponseStatusCode; }
            set { m_ResponseStatusCode = value; }
        }
        public string ResponseDescription
        {
            get;
            set;
        }
        private DateTime m_ResponseLastModified = System.DateTime.MaxValue;
        public DateTime ResponseLastModified
        {
            get { return m_ResponseLastModified; }
            set { m_ResponseLastModified = value; }
        }
        public long ResponseLength { get; set; }
        #endregion

        public HttpClient(IPAddress ipAddr)
        {
            m_localIp = ipAddr;
            TimeOut = DEFAULT_TIMEOUT;
        }

        public HttpClient(string ip)
        {
            IPAddress ipAdd;
            if (!String.IsNullOrEmpty(ip) && IPAddress.TryParse(ip, out ipAdd))
            {
                m_localIp = ipAdd;
            }
            TimeOut = DEFAULT_TIMEOUT;
        }

        public HttpClient()
            : this("")
        {

        }

        private IPEndPoint WebBindIPEndPoint(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
        {
            return new IPEndPoint(m_localIp, 0);
        }

        /// <summary>
        /// 执行Web请求，返回WebResponse
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected virtual HttpWebResponse GetWebResponse(HttpWebRequest request)
        {
            HttpWebResponse response = null;
            try
            {
                ClearState();
                response = (HttpWebResponse)request.GetResponse();
                CompleteState(response);
            }
            finally
            {
                if (response != null)
                {
                    if (!string.IsNullOrEmpty(response.CharacterSet))
                        this.Encode = Encoding.GetEncoding(response.CharacterSet);
                }
            }

            return response;
        }

        protected virtual HttpWebRequest GetWebRequest(Uri uri)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            request.UserAgent = DEFAULT_USERAGENT;
            request.ContentType = string.IsNullOrEmpty(this.ContentType) ? DEFAULT_CONTENTTYPE : this.ContentType;
            //request.Accept = "";
            request.AllowAutoRedirect = this.AutoRedirect;
            request.Timeout = TimeOut;
            request.Method = "GET";
            request.Headers.Add(HttpRequestHeader.AcceptLanguage, "zh-cn");
            request.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip, deflate");
            //request.Proxy=new WebProxy(
            if (this.Proxy != null)
                request.Proxy = this.Proxy;

            if (!string.IsNullOrEmpty(this.Referer))
            {
                request.Referer = this.Referer;
            }
            if (this.Credentials != null)
            {
                request.Credentials = Credentials;
            }

            if (this.RequestHeaders != null)
            {
                for (int i = 0; i < this.RequestHeaders.Count; i++)
                {
                    string key = this.RequestHeaders.GetKey(i);
                    string value = this.RequestHeaders[i];
                    switch (key.ToLower())
                    {
                        case "user-agent":
                            request.UserAgent = value;
                            break;
                        case "if-modified-since":
                            request.IfModifiedSince = DateTime.Parse(value);
                            break;
                        case "accept":
                            request.Accept = value;
                            break;
                        case "referer":
                            request.Referer = value;
                            break;
                        case "content-type":
                            request.ContentType = value;
                            break;
                        default:
                            request.Headers.Set(key, value);
                            break;
                    }
                }
            }

            if (this.CookieContainer != null)
                request.CookieContainer = this.CookieContainer;
            if (HttpContext.Current != null &&
                HttpContext.Current.Request != null &&
                HttpContext.Current.Request.Headers != null &&
                HttpContext.Current.Request.Headers["Cookie"] != null
                )
                request.CookieContainer.SetCookies(uri, HttpContext.Current.Request.Headers["Cookie"]);

            //当要POST的数据大于1024字节的时候, POST请求 分两步
            //1. 发送一个请求, 包含一个Expect:100-continue, 询问Server使用愿意接受数据 
            //2. 接收到Server返回的100-continue应答以后, 才把数据POST给Server 把这个取消
            request.ServicePoint.Expect100Continue = false;
            request.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(WebBindIPEndPoint);
            return request;
        }



        /// <summary>
        /// 执行Web请求，Web内容以字串返回 ,内部自动检测编码
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public virtual String GetString(string url)
        {
            if (url == null)
                throw new ArgumentNullException("url");

            HttpWebRequest result = this.GetWebRequest(GetUri(url));
            byte[] ret = GetRawBytesData(result);
            Encoding encode = this.Encode;

            if (encode == null)
            {
                string rawStr = Encoding.Default.GetString(ret);
                encode = GetEncoding(rawStr);
                if (encode == Encoding.Default)
                {
                    return rawStr;
                }
                else
                {
                    return encode.GetString(ret);
                }
            }
            else
            {
                return encode.GetString(ret);
            }
        }

        public virtual byte[] GetBytes(string url)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            HttpWebRequest result = this.GetWebRequest(GetUri(url));
            return GetRawBytesData(result);
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="fileName"></param>
        public virtual void GetFile(string url, string fileName)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            if (fileName == null)
                throw new ArgumentNullException("fileName");

            HttpWebRequest request = null;
            FileStream fs = null;
            bool succeeded = false;
            try
            {
                request = GetWebRequest(GetUri(url));
                fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
                Stream readStream = this.GetStream(request);
                DownBits(fs, readStream);
                if (readStream != null)
                {
                    readStream.Close();
                }
                fs.Close();
                succeeded = true;
            }
            catch (Exception e)
            {

                AbortRequest(request);
                throw e;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                    if (!succeeded)
                    {
                        File.Delete(fileName);
                    }
                    fs = null;
                }
            }
        }

        private void DownBits(Stream writeStream, Stream readStream)
        {
            long length;
            long contentLength = this.ResponseLength;
            if (contentLength == -1 || contentLength > DEFAULT_DOWNLOAD_BUFFER_LENGTH)
                length = DEFAULT_DOWNLOAD_BUFFER_LENGTH;
            else
                length = contentLength;
            byte[] buffer = new byte[(int)length];
            if (readStream != null && readStream != Stream.Null)
            {
                int read = 0;
                while ((read = readStream.Read(buffer, 0, (int)length)) > 0)
                {
                    writeStream.Write(buffer, 0, read);
                }
            }
        }
        /// <summary>
        /// 执行Web请求，返回Web内容的字节内容 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private byte[] GetRawBytesData(HttpWebRequest request)
        {
            byte[] responseBytes = new byte[1024];
            using (Stream s = GetStream(request))
            {
                if (this.ResponseLength > Int32.MaxValue)
                {
                    throw new WebException("下载内容长度为" + this.ResponseLength + "超出的内存限制,请使用DownFile");
                }

                if (s == null) return new byte[0];

                MemoryStream m = new MemoryStream();
                DownBits(m, s);
                //using (BinaryReader r = new BinaryReader(s))
                //{
                //    byte[] buffer = new byte[1024];
                //    while (true)
                //    {
                //        int read = r.Read(buffer, 0, 1024);
                //        if (read > 0)
                //        {
                //            m.Write(buffer, 0, read);
                //        }
                //        else
                //        {
                //            break;
                //        }
                //    }
                //}
                return m.ToArray();
            }
        }

        /// <summary>
        /// 返回的流不被关闭，使用此方法，一定要自己关闭返回的流，否则，后果自负 
        /// using(Stream s in executor.GetStream(url))
        /// {
        ///    //TODO
        /// }
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private Stream GetStream(HttpWebRequest request)
        {
            HttpWebResponse response = GetWebResponse(request);
            string contentEncoding = response.Headers[HttpResponseHeader.ContentEncoding];

            if (response != null)
            {
                Stream stream = response.GetResponseStream();
                if (!string.IsNullOrEmpty(contentEncoding))
                {
                    if ("gzip".Equals(contentEncoding, StringComparison.OrdinalIgnoreCase))
                    {
                        GZipStream gzip = new GZipStream(stream, CompressionMode.Decompress);

                        return gzip;
                    }
                    if ("deflate".Equals(contentEncoding, StringComparison.OrdinalIgnoreCase))
                    {
                        DeflateStream deflate = new DeflateStream(stream, CompressionMode.Decompress);
                        return deflate;
                    }
                }
                else
                {
                    return stream;
                }
            }
            return null;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="url"></param>
        /// <param name="values"></param>
        /// <param name="files"></param>
        /// <returns></returns>
        public virtual byte[] PostValues(string url, NameValueCollection values, NameValueCollection files)
        {
            if (this.Encode == null)
                throw new InvalidOperationException("不能确定编码类型。或先通过 Get 自动实别出来。或手工指定");

            if (url == null)
                throw new ArgumentNullException("url");
            if (values == null)
                throw new ArgumentNullException("values");
            if (files == null)
                throw new ArgumentNullException("files");

            HttpWebRequest request = null;
            if (files.Count > 0)
            {
                Uri uri = GetUri(url);
                string contentType = DEFAULT_UPLOADFILE_CONTENTTYPE;
                long contentLength = 0;
                bool isNegative = false;
                string boundary = "---------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
                byte[] formFooterBytes = Encoding.ASCII.GetBytes("--" + boundary + "--\r\n");
                byte[] newLineBytes = Encoding.ASCII.GetBytes("\r\n");
                if (this.RequestHeaders == null)
                    this.RequestHeaders = new WebHeaderCollection();
                this.RequestHeaders.Add(HttpRequestHeader.ContentType, UPLOAD_FILE_CONTENTTYPE + "; boundary=" + boundary);
                request = this.GetWebRequest(uri);
                request.Method = "POST";//must

                Stream writeStream = request.GetRequestStream();
                Stream stream = null;
                try
                {
                    #region 写File
                    foreach (var f in files.AllKeys)
                    {
                        string fileName = Path.GetFullPath(files[f]);

                        string formHeader = "--" + boundary + "\r\n"
                            + "Content-Disposition: form-data; name=\"" + f + "\"; filename=\"" + Path.GetFileName(fileName) + "\"\r\n"
                            + "Content-Type: " + contentType + "\r\n"
                            + "\r\n";
                        byte[] formHeaderBytes = this.Encode.GetBytes(formHeader);

                        stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                        contentLength += formHeaderBytes.Length;
                        writeStream.Write(formHeaderBytes, 0, formHeaderBytes.Length);//写头
                        long writeLen = PutBits(writeStream, stream);
                        if (writeLen != -1)
                        {
                            contentLength += writeLen;
                        }
                        else
                        {
                            isNegative = true;//-1
                        }

                        if (stream != null)
                        {
                            stream.Close();
                        }
                        writeStream.Write(newLineBytes, 0, newLineBytes.Length);
                        contentLength += newLineBytes.Length;
                    }
                    #endregion

                    #region 写Value
                    foreach (var v in values.AllKeys)
                    {
                        string value = values[v];

                        string formHeader = "--" + boundary + "\r\n"
                            + "Content-Disposition: form-data; name=\"" + v + "\"\r\n"
                            + "Content-Type: " + DEFAULT_CONTENTTYPE + "\r\n"
                            + "\r\n";
                        byte[] formHeaderBytes = this.Encode.GetBytes(formHeader);
                        contentLength += formHeaderBytes.Length;
                        //StringReader reader=
                        stream = new MemoryStream(this.Encode.GetBytes(value));
                        writeStream.Write(formHeaderBytes, 0, formHeaderBytes.Length);
                        long writeLen = PutBits(writeStream, stream);
                        if (writeLen != -1)
                        {
                            contentLength += writeLen;
                        }
                        else
                        {
                            isNegative = true;//-1
                        }
                        if (stream != null)
                        {
                            stream.Close();
                        }
                        writeStream.Write(newLineBytes, 0, newLineBytes.Length);
                        contentLength += newLineBytes.Length;
                    }
                    #endregion

                    contentLength += formFooterBytes.Length;
                    writeStream.Write(formFooterBytes, 0, formFooterBytes.Length);
                    if (isNegative)
                        contentLength = -1;
                }
                catch
                {

                }
                finally
                {
                    if (writeStream != null)
                        writeStream.Close();
                    if (stream != null)
                        stream.Close();
                }
                return this.GetRawBytesData(request);
            }
            else
            {
                return PostValues(url, values);
            }
        }

        public virtual byte[] PostValues(string url, NameValueCollection values)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            if (values == null)
                throw new ArgumentNullException("values");
            string data = GetParameterString(values);
            return PostString(url, data);
        }

        public virtual byte[] PostFile(string url, string fileName)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            if (fileName == null)
                throw new ArgumentNullException("fileName");

            return PostValues(url, new NameValueCollection(), new NameValueCollection() { { "file", fileName } });
            /*
            FileStream fs = null;
            HttpWebRequest request = null;
            try
            {
                byte[] formHeaderBytes = null, boundaryBytes = null, buffer = null;
                Uri uri = GetUri(url);
                fileName = Path.GetFullPath(fileName);
                if (this.RequestHeaders == null)
                    this.RequestHeaders = new WebHeaderCollection();
                //string contentType = this.RequestHeaders["Content-Type"];
                string contentType = DEFAULT_UPLOADFILE_CONTENTTYPE;

                fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                int buffSize = DEFAULT_COPY_BUFFER_LENGTH;
                long contentLength = -1;

                string boundary = "---------------------" + DateTime.Now.Ticks.ToString("x", NumberFormatInfo.InvariantInfo);
                this.RequestHeaders.Add(HttpRequestHeader.ContentType, UPLOAD_FILE_CONTENTTYPE + "; boundary=" + boundary);

                string formHeader = "--" + boundary + "\r\n"
                                + "Content-Disposition: form-data; name=\"file\"; filename=\"" + Path.GetFileName(fileName) + "\"\r\n"
                                + "Content-Type: " + contentType + "\r\n"
                                + "\r\n";
                formHeaderBytes = Encoding.UTF8.GetBytes(formHeader);
                boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");

                if (fs.CanSeek)
                {
                    contentLength = fs.Length + formHeaderBytes.Length + boundaryBytes.Length;
                    buffSize = (int)Math.Min((long)DEFAULT_COPY_BUFFER_LENGTH, fs.Length);
                }

                buffer = new byte[buffSize];

                request = this.GetWebRequest(uri);
                request.Method = "POST";
                // request.AllowWriteStreamBuffering = false;
                Stream writeStream = request.GetRequestStream();

                byte[] bytesToWrite = formHeaderBytes;
                formHeaderBytes = null;
                writeStream.Write(bytesToWrite, 0, bytesToWrite.Length);//写头

                if (fs != null)
                {
                    while (true)
                    {
                        int bytesRead = 0;
                        int bytesToWriteLength = 0;
                        bytesToWrite = null;
                        if (buffer != null)
                        {
                            bytesRead = fs.Read(buffer, 0, (int)buffer.Length);
                            if (bytesRead <= 0)
                            {
                                fs.Close();
                                buffer = null;
                            }
                        }
                        if (buffer != null)
                        {
                            bytesToWrite = buffer;
                            bytesToWriteLength = bytesRead;
                        }
                        else if (boundaryBytes != null)//Footer
                        {
                            bytesToWriteLength = boundaryBytes.Length;
                            bytesToWrite = boundaryBytes;
                            boundaryBytes = null;
                        }
                        else
                        {
                            break;
                        }
                        writeStream.Write(bytesToWrite, 0, bytesToWriteLength);
                    }
                }
                if (fs != null)
                    fs.Close();
                if (writeStream != null) //一定要关闭。不关闭不能上传
                    writeStream.Close();
                return this.GetRawBytesData(request);
            }
            catch
            {
                throw;
            }
            finally
            {

            }
             * */
        }

        public virtual byte[] PostString(string url, string data)
        {
            if (this.Encode == null)
                throw new InvalidOperationException("不能确定编码类型。或先通过 Get 自动实别出来。或手工指定");

            HttpWebRequest request = null;
            Stream stream = null;
            Stream writeSteam = null;
            try
            {
                request = GetWebRequest(GetUri(url));
                request.Method = "POST";
                stream = new MemoryStream(this.Encode.GetBytes(data));
                writeSteam = request.GetRequestStream();
                long writeLen = PutBits(writeSteam, stream);
                if (stream != null)
                    stream.Close();
                if (writeSteam != null)
                    writeSteam.Close();
                //request.ContentLength = writeLen;

                return this.GetRawBytesData(request);
            }
            catch
            {
                throw;
            }
            finally
            {
                if (stream != null)
                    stream.Close();
                if (writeSteam != null)
                    writeSteam.Close();
            }
        }

        public virtual string PostValuesReturnString(string url, NameValueCollection values, NameValueCollection files)
        {
            return this.Encode.GetString(this.PostValues(url, values, files));
        }

        public virtual string PostValuesReturnString(string url, NameValueCollection values)
        {
            return this.Encode.GetString(this.PostValues(url, values));
        }

        public virtual string PostFileReturnString(string url, string fileName)
        {
            return this.Encode.GetString(this.PostFile(url, fileName));
        }

        public virtual string PostStringReturnString(string url, string data)
        {
            return this.Encode.GetString(this.PostString(url, data));
        }

        private long PutBits(Stream writeSteam, Stream readStream)
        {
            if (readStream == null) return 0;

            byte[] buffer = null;
            int bufferSize = DEFAULT_COPY_BUFFER_LENGTH;
            long contentLength = -1;
            if (readStream.CanSeek)
            {
                contentLength = readStream.Length;
                bufferSize = (int)Math.Min((long)DEFAULT_COPY_BUFFER_LENGTH, readStream.Length);
            }
            buffer = new byte[bufferSize];
            int readBytes = 0;
            while ((readBytes = readStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                writeSteam.Write(buffer, 0, readBytes);
            }
            return contentLength;
        }

        private Uri GetUri(string url)
        {
            Uri uri;
            Uri.TryCreate(url, UriKind.Absolute, out uri);
            return uri;
        }

        private Encoding GetEncoding(string content)
        {
            Match charSetMatch = m_regCharSet.Match(content);
            string webCharSet = charSetMatch.Groups[2].Value;
            Encoding encode = null;
            if (!string.IsNullOrEmpty(webCharSet))
            {
                encode = Encoding.GetEncoding(webCharSet);
            }
            else
            {
                encode = Encoding.Default;
            }
            //保存，检测出来Encode
            this.Encode = encode;
            return encode;
        }

        private void AbortRequest(WebRequest request)
        {
            try
            {
                if (request != null)
                {
                    request.Abort();
                }
            }
            catch (Exception exception)
            {
                if (exception is OutOfMemoryException || exception is StackOverflowException || exception is ThreadAbortException)
                {
                    throw;
                }
            }
            catch
            {

            }
        }

        private void ClearState()
        {
            this.ResponseHeaders = null;
            this.ResponseStatusCode = HttpStatusCode.OK;
            this.ResponseDescription = null;
            this.ResponseLastModified = System.DateTime.MaxValue;
            this.ResponseLength = -1;
        }

        private void CompleteState(HttpWebResponse response)
        {
            // get properties
            this.ResponseLength = response.ContentLength;
            this.ResponseStatusCode = response.StatusCode;
            this.ResponseDescription = response.StatusDescription;
            this.ResponseLastModified = response.LastModified;

            // get headers
            this.ResponseHeaders = response.Headers;

            // get cookies
            //this.CookieContainer = new CookieContainer();
            //foreach (Cookie ck in response.Cookies)
            //    this.CookieContainer.Add(ck);

        }

        private string GetParameterString(NameValueCollection nv)
        {
            string data = "";
            foreach (string n in nv)
            {
                data += n + "=" + HttpUtility.UrlEncode(nv[n], this.Encode) + "&";
            }
            return data.TrimEnd('&');
        }
    }
}
